package ch.exm.storm.query.hibernate;

import static ch.exm.storm.conditions.arguments.ArgumentsFactory.getActualArgument;
import static ch.exm.storm.introspection.IntrospectionUtil.getPropertyNameFromAccessor;

import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

import org.hibernate.MappingException;
import org.hibernate.SessionFactory;
import org.hibernate.cfg.Configuration;
import org.hibernate.mapping.PersistentClass;
import org.hibernate.mapping.Property;

import ch.exm.storm.conditions.arguments.Argument;
import ch.exm.storm.conditions.arguments.EvaluatedArgument;
import ch.exm.storm.conditions.arguments.ExpressionEvaluatedArgument;
import ch.exm.storm.conditions.arguments.Invocation;
import ch.exm.storm.log.Logger;
import ch.exm.storm.query.selection.EvaluatedRelationalSelection;
import ch.exm.storm.query.selection.Selection;

class HibernateConfiguration {
	
	private Configuration configuration;

	private Map<Class<?>, PersistentClass> classMapping = new HashMap<Class<?>, PersistentClass>();

	HibernateConfiguration() { this(null); }
	
	HibernateConfiguration(Configuration configuration) {
		this.configuration = configuration;
		parseConfiguration();
	}

	SessionFactory getSessionFactory() {
		return getConfiguration().buildSessionFactory();
	}
	
	PersistentClass getMappedClass(Class<?> clazz) {
		return classMapping.get(clazz);
	}
	
	private void parseConfiguration() {
		Iterator<?> mappingIterator = getConfiguration().getClassMappings();
		while (mappingIterator.hasNext()) {
			addMappedClass((PersistentClass)mappingIterator.next());
		}
	}
	
	private void addMappedClass(PersistentClass mappedClass) {
		Logger.debug(this, "Parsing persistent class: " + mappedClass);
		classMapping.put(mappedClass.getMappedClass(), mappedClass);
		Class<?> proxyClass = mappedClass.getProxyInterface();
		if (proxyClass != null) classMapping.put(proxyClass, mappedClass);
	}
	
	private Configuration getConfiguration() {
		if (configuration == null) {
			configuration = new Configuration().configure();
//			configuration.addResource("hibernate.cfg.xml");
			configuration.buildMappings();
		}
		return configuration;
	}
	
	EvaluatedRelationalSelection evaluateSelection(HibernateQuery query, Selection selection) {
		Argument argument = getActualArgument(selection.getSelected());
		return argument == null ? null : new EvaluatedRelationalSelection(selection, evaluateArgument(query, argument));
	}
	
	EvaluatedArgument evaluateArgument(HibernateQuery query, Argument argument) {
		ExpressionEvaluatedArgument evaluatedArgument = new ExpressionEvaluatedArgument(argument);
		StringBuilder sb = new StringBuilder();
		
		Iterator<Invocation> invocationIterator = argument.iterator();
		while (invocationIterator.hasNext()) {
			Invocation invocation = invocationIterator.next();
			Class<?> invokedClass = invocation.getInvokedClass();
			Method invokedMethod = invocation.getInvokedMethod();
			if (invokedMethod == null) break;

			PersistentClass mappedClass = getMappedClass(invokedClass);
			String propertyName = getPropertyNameFromAccessor(invokedMethod.getName());
			if (propertyName == null || getMappedProperty(mappedClass, propertyName) == null) {
				evaluatedArgument.addNonRelationalInvocations(invocation);
				while (invocationIterator.hasNext()) evaluatedArgument.addNonRelationalInvocations(invocationIterator.next());
				break;
			}
			sb.append(".").append(propertyName);
		}
		
		String evaluatedPart = query.addSelectedArgument(argument) + sb.toString();
		evaluatedArgument.setEvaluatedPart(evaluatedPart);
		return evaluatedArgument;
	}
	
	private Property getMappedProperty(PersistentClass mappedClass, String propertyName) {
		try {
			return mappedClass.getProperty(propertyName);
		} catch (MappingException me) { }
		return null;
	}
}
